using System;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries.Mathematics;

namespace Atomic.Structures
{
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class HexagonalLattice : Lattice
	{
		public HexagonalLattice(double a, double c)
			: this(Basis.StandardBasis, a, c, SpaceMatrix.Identity)
		{
		}

		public HexagonalLattice(Basis basis, double a, double c, SpaceMatrix transform)
			: this(basis.Decompose(new Position(transform * new SpaceVector(1.0, 0.0, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.5, 0.86602540378443865, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.0, 0.0, 1.0) * c)), a, c, transform)
		{
			// http://lamp.tu-graz.ac.at/~hadley/ss1/bzones/hexagonal.php
		}

		private HexagonalLattice(Position a, Position b, Position c, double a0, double c0, SpaceMatrix transform)
			: base(a, b, c)
		{
			if (a0 <= 0.0 || c0 <= 0.0)
			{
				throw new ArgumentException();
			}

			CheckOrthogonal(transform);

			A0 = a0;
			C0 = c0;
			Transform = transform;

			Gamma = new Position(this, SymmetryPoints.Gamma);
			A_ = new Position(this, SymmetryPoints.A);
			K = new Position(this, SymmetryPoints.K);
			H = new Position(this, SymmetryPoints.H);
			M = new Position(this, SymmetryPoints.M);
			L = new Position(this, SymmetryPoints.L);
		}
		
		public static bool TryParse(Basis basis, SpaceVector a, SpaceVector b, SpaceVector c, out Lattice lattice)
		{
			lattice = null;

			// Chosen to be somewhat larger than VASP's output precision (~1.0e-8).
			double tol = 1.0e-5; // 1.0e-7

			// The reference lattice built directly from the vectors.
			Lattice lattice0 = new Lattice(basis, a, b, c);

			// Try creating a fcc lattice. Usually the a and c lattice constants are the first and last entry, respectively.
			double a0 = Math.Abs(lattice0.A.PhysicalCoordinates[0]);
			double c0 = Math.Abs(lattice0.C.PhysicalCoordinates[2]);
			if (a0 <= tol || c0 <= tol)
			{
				// Far too small lattice constants. Give up trying to parse.
				lattice = null;
				return false;
			}

			// Try a few common fcc representations. May need to add more.
			foreach (SpaceMatrix transform in new SpaceMatrix[] { SpaceMatrix.Identity, new SpaceMatrix(1.0, -1.1547005383792515, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0), new SpaceMatrix(-1.0, 1.1547005383792515, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0), new SpaceMatrix(-0.8660254037844386, 0.5, 0.0, -0.5, -0.8660254037844386, 0.0, 0.0, 0.0, 1.0)/*, new SpaceMatrix(-0.8660254037844386, -0.5, 0.0, 0.5, -0.8660254037844386, 0.0, 0.0, 0.0, 1.0)*/ })
			{
				lattice = new HexagonalLattice(basis, a0, c0, transform);
				if (SpaceMatrix.Norm(lattice0.PhysicalMap - lattice.PhysicalMap) < tol)
				{
					// The mapping is identical. Parsed successfully.
					return true;
				}
			}

			//lattice = new HexagonalLattice(basis, a0, c0, SpaceMatrix.Identity);
			//SpaceMatrix transform = lattice0.PhysicalMap * lattice.InversePhysicalMap;
			//Console.WriteLine(((Matrix)transform).ToString("r"));

			return false;
		}

		protected override Lattice CreateReciprocalLattice()
		{
			return new HexagonalLattice(Basis.StandardBasis, 4.0 * Math.PI / (Math.Sqrt(3.0) * A0), 2.0 * Math.PI / C0, Transform * new SpaceMatrix(0.86602540378443849, -0.5, 0.0, -0.5, 1.4433756729740645, 0.0, 0.0, 0.0, 1.0));
		}

		/// <summary>
		/// First hcp lattice constant.
		/// </summary>
		public double A0
		{
			get;
			private set;
		}

		/// <summary>
		/// Second hcp lattice constant.
		/// </summary>
		public double C0
		{
			get;
			private set;
		}

		/// <summary>
		/// The orthogonal transform matrix from the standard lattice vector representation.
		/// </summary>
		public SpaceMatrix Transform
		{
			get;
			private set;
		}

		public Position Gamma
		{
			get;
			private set;
		}

		public Position A_
		{
			get;
			private set;
		}

		public Position K
		{
			get;
			private set;
		}

		public Position H
		{
			get;
			private set;
		}

		public Position M
		{
			get;
			private set;
		}

		public Position L
		{
			get;
			private set;
		}

		private string DebuggerDisplay
		{
			get
			{
				return string.Format(CultureInfo.InvariantCulture, "hcp, A = ({0:f4}, {1:f4}, {2:f4}), B = ({3:f4}, {4:f4}, {5:f4}), C = ({6:f4}, {7:f4}, {8:f4})",
					A.PhysicalCoordinates.X, A.PhysicalCoordinates.Y, A.PhysicalCoordinates.Z, 
					B.PhysicalCoordinates.X, B.PhysicalCoordinates.Y, B.PhysicalCoordinates.Z, 
					C.PhysicalCoordinates.X, C.PhysicalCoordinates.Y, C.PhysicalCoordinates.Z);
			}
		}

		public static class SymmetryPoints
		{
			static SymmetryPoints()
			{
				Gamma = new SpaceVector(0.0, 0.0, 0.0);
				A = new SpaceVector(0.0, 0.0, 0.5);
				K = new SpaceVector(0.66666666666666663, 0.33333333333333331, 0.0);
				H = new SpaceVector(0.66666666666666663, 0.33333333333333331, 0.5);
				M = new SpaceVector(0.5, 0.0, 0.0);
				L = new SpaceVector(0.5, 0.0, 0.5);
			}

			public static SpaceVector Gamma
			{
				get;
				private set;
			}

			public static SpaceVector A
			{
				get;
				private set;
			}

			public static SpaceVector K
			{
				get;
				private set;
			}

			public static SpaceVector H
			{
				get;
				private set;
			}

			public static SpaceVector M
			{
				get;
				private set;
			}

			public static SpaceVector L
			{
				get;
				private set;
			}
		}
	}
}
